package chap_01_CompletableFuture.step_04_LockSupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description ReentrantLock 的 Condition 实现阻塞、唤醒
 * @Author shihan
 * @Date 2022/10/23 10:10
 * @Version 1.0
 */
public class LockSupport_02_Condition {

    /**
     * 局限性：
     * 1、Condition 必须要在 ReentrantLock 的 lock、unlock 对中使用；
     * 2、Condition 的 await、signal 必须成对出现，并且如果 await 后没有 signal 出现，则线程将被一直阻塞；
     * 3、还有一个隐含的知识点（synchronized的 wait、notify 也是一样的）：
     * 3.1、当前发出的 notify、notifyAll、signal、signalAll 会立即到等待队列里去找唤醒的线程，如果此时没有等待的线程，那么唤醒动作将执行结束；
     * 3.2、也就是说，唤醒动作是过时不候的；
     *
     * @param args
     */
    public static void main(String[] args) {
        Demo2 lock = new Demo2();
        // 案例：实现两个线程之间的交替输出打印
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                if (i == 4) {
                    lock.printA(true);
                } else {
                    lock.printA(false);

                }
            }
        }, "t1").start();

        // 延迟 100 毫秒，保证第一个先执行（思考：如果不 sleep 有什么办法能保证第一个线程先执行吗？待后面学习更多内容后，再来回答这个问题！）
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                if (i == 4) {
                    lock.printB(true);
                } else {
                    lock.printB(false);

                }
            }
        }, "t2").start();
    }

}

class Demo2 {

    public static final String[] zimu = new String[]{"a", "b", "c", "d", "e"};
    public static final String[] shuzi = new String[]{"1", "2", "3", "4", "5"};
    private int z = 0;
    private int s = 0;

    /**
     * 回顾：
     * 1、synchronized 的锁对象是明确的，要么是 this 要么是 Class 对象；
     * 2、ReentrantLock 要想能够锁住，就必须要用同一个 ReentrantLock 对象进行 lock、unlock 包裹；
     * （在还不懂底层原理的情况下，先这么理解，其实和 synchronized 很像）
     * 3、Condition 可以实现分组的阻塞、唤醒，并且是通过 ReentrantLock 对象的 newCondition 方法获取的；
     * 4、关于 ReentrantLock 的加锁方法回顾：
     * 4.1、lock()：获取不到锁将会一直等待，直到获取到锁，使用不当及其容易产生死锁（不推荐使用）；
     * 4.2、tryLock()：
     * --------oneTry 只尝试获取锁一次，获取成功返回true，获取失败返回false（常规场景推荐使用）；
     * --------并且打破 fair 机制，即使前面还有等待的线程，只要当前执行 tryLock 的线程返回true能够获取锁，则将直接获取锁；
     * 4.3、tryLock(long,timeUnit)：
     * --------在给定时间内，如果获取不到锁，将进行获取锁的重试，获取到锁返回true，超过给定时间或者被其他线程 interrupt 中断将会返回 false；
     * --------能够保证 fair 机制，即当前线程本来能够获取到锁，但是因为前面还有等待的线程，所以将放弃本次获取到的锁，让给等待队列最前面的那个线程；
     * --------如果参数设置的重试时间 <=0 则不再重试（此时相当于能够保证 fair 机制的 tryLock() 方法调用）
     */
    private ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    /**
     * 为什么要加 isEnd 呢，是因为最后一次的时候，其实是不需要 wait 自己的，主要是这个原因
     *
     * @param isEnd
     */
    public void printA(boolean isEnd) {
        lock.tryLock();
        try {
            System.out.println(Thread.currentThread().getName() + ":" + zimu[z++]);
            condition.signalAll();
            if (!isEnd) {
                condition.await();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public synchronized void printB(boolean isEnd) {
        lock.tryLock();
        try {
            System.out.println(Thread.currentThread().getName() + ":" + shuzi[s++]);
            condition.signalAll();
            if (!isEnd) {
                condition.await();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}
